package org.haredot.filter;


import org.apache.commons.io.IOUtils;
import org.haredot.media.MediaType;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URLDecoder;
import java.util.*;

/**
 * 解决 PUT , DELETE , PATCH 请求 无法获取 表单 参数
 *
 *    application/x-www-form-urlencoded
 *    application/x-www-form-urlencoded;charset=utf-8
 *
 */
public class FormContentFilter implements Filter {
    /**
     * 要处理的请求类型
     */
    private static final List<String> allowMethods = List.of("PUT", "DELETE", "PATCH");

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req ;
        HttpServletResponse response = (HttpServletResponse) resp ;

        // 判断 当前请求方式 是否是 需要处理的请求
        if (!allowMethods.contains(request.getMethod())) {
            // 继续向下一个过滤器执行代理
            chain.doFilter(request, response);
            return ;
        }
        // 获取 请求的 方式
        String contentType = request.getContentType();

        if (contentType == null || "".equals(contentType)) {
            // 继续向下一个过滤器执行代理
            chain.doFilter(request, response);
            return ;
        }
        // 判断 请求方式 是否是 表单请求
        MediaType mediaType = MediaType.of(contentType);

        if (!mediaType.getType().equals(MediaType.APPLICATION_X_WWW_FORM_URLENCODED_STRING)) {
            // 继续向下一个过滤器执行代理
            chain.doFilter(request, response);
            return ;
        }

        // 此时说明请求是需要处理参数的
        ServletInputStream inputStream = request.getInputStream();

        byte[] bytes = IOUtils.toByteArray(new InputStreamReader(inputStream), mediaType.getCharset());
        String params = new String(bytes, mediaType.getCharset());

        // 根据 & 拆分参数 params
        String[] split = params.split("&");

        Map<String, List<String>> mapValues = new HashMap<>();
        for(String keyValStr : split) {
            // 根据 = 拆分 键和值
            String[] split1 = keyValStr.split("=");

            String key = URLDecoder.decode(split1[0], mediaType.getCharset()) ;
            String val = URLDecoder.decode(split1[1], mediaType.getCharset()) ;

            if (!mapValues.containsKey(key)) {
                mapValues.put(key, new ArrayList<>(List.of(val)));
            }else{
                mapValues.get(key).add(val);
            }
        }
        // mapValues 中放的是 最终解析后的数据, 将 request 进行包装 继续向下执行
        chain.doFilter(new FormContentHttpServletRequestWrapper(request, mapValues) , response);

    }


    private static class FormContentHttpServletRequestWrapper extends HttpServletRequestWrapper {

        private final Map<String, List<String>> mapValues ;

        public FormContentHttpServletRequestWrapper(HttpServletRequest request, Map<String, List<String>> mapValues) {
            super(request);
            this.mapValues = mapValues ;
        }

        @Override
        public String getParameter(String name) {
            // 从 父类中获取
            String val = super.getParameter(name);

            if (val !=null) return val ;
            // 如果 父类中获取不到 ，在从
            List<String> strings = this.mapValues.get(name);

            if (strings !=null) return strings.get(0);

            return null ;
        }

        /**
         * 获取所有的参数
         * @return
         */
        public Map<String, String[]> getParameterMap() {

            Map<String, String[]> map = new HashMap<>();
            // 获取所有的键
            Enumeration<String> parameterNames = this.getParameterNames();

            while(parameterNames.hasMoreElements()) {

                String key = parameterNames.nextElement();

                // 根据键 获取值
                String[] parameterValues = this.getParameterValues(key);

                map.put(key, parameterValues);
            }
            return Map.copyOf(map);
        }

        /**
         * 获取所有参数的键
         * @return
         */
        public Enumeration<String> getParameterNames() {
            // 获取 request 本身能够得到的数据
            Enumeration<String> parameterNames = super.getParameterNames();

            Set<String> set = new HashSet<>();

            while (parameterNames.hasMoreElements()) {
                String key = parameterNames.nextElement();
                set.add(key);
            }
            // 编码 mapValues
            Set<String> strings = mapValues.keySet();
            set.addAll(strings);
            return Collections.enumeration(set);
        }

        @Override
        public String[] getParameterValues(String name) {
            List<String> list = new ArrayList<>();
            // 从 父类中和 获取 键对应的 值
            String[] parameterValues = super.getParameterValues(name);

            if (parameterValues != null) {
                Arrays.stream(parameterValues).forEach(list::add);
            }

            // 再次从 mapValues 中获取数据
            List<String> strings = this.mapValues.get(name);

            if (strings != null) {
                list.addAll(strings);
            }
            return list.toArray(new String[list.size()]);
        }

        @Override
        public ServletInputStream getInputStream() throws IOException {
            return null ;
        }
    }
}
